///////////////////////////////////////////////////////
// Code author: Martin Lapierre, http://devinstinct.com
///////////////////////////////////////////////////////

using System;
using System.CodeDom;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using SubSonic.Utilities;

namespace SubSonic
{
    /// <summary>
    /// Provides the features required for code generation. 
    /// </summary>
    public interface ICompileUnitGenerator
    {
        #region Methods

        /// <summary>
        /// Generates the DAL in a single CodeCompileUnit from an input file.
        /// </summary>
        /// <param name="languageType">The language to generate the DAL for.</param>
        /// <param name="inputFile">The path to the input file.</param>
        /// <returns>A single CodeCompileUnit.</returns>
        CodeCompileUnit GenerateSingleUnit(LanguageType languageType, string inputFile);

        /// <summary>
        /// Generates the DAL in multiple CodeCompileUnits from an input file.
        /// </summary>
        /// <param name="languageType">The language to generate the DAL for.</param>
        /// <param name="inputFile">The path to the input file.</param>
        /// <returns>A dictionary of suggested filenames/CodeCompileUnit pairs.</returns>
        IDictionary<string, CodeCompileUnit> GenerateMultipleUnits(LanguageType languageType, string inputFile);

        /// <summary>
        /// Generates the DAL in multiple CodeCompileUnits for specific tables, views
        /// and, optionnaly, all stored procedures.
        /// </summary>
        /// <param name="languageType">The language to generate the DAL for.</param>
        /// <param name="tables">
        /// The list of tables to generate the DAL for; first element as "*" for all tables.
        /// Can be null or empty. A null value generates no table and no table struct. 
        /// <param name="views">
        /// The list of views to generate the DAL for; first element as "*" for all views.
        /// Can be null or empty. A null value generates no view and no view struct. 
        /// </param>
        /// <param name="useSPs">
        /// true to generate the Stored Procedure; false not to generate.
        /// A null value generates Stored Procedures based on the configuration file option.
        /// </param>
        /// <param name="providerName">
        /// The name of the provider to generate the DAL for.
        /// If null, generates code for all the providers.
        /// </param>
        /// <returns>A dictionary of suggested filenames/CodeCompileUnit pairs.</returns>
        IDictionary<string, CodeCompileUnit> GenerateMultipleUnits(LanguageType languageType, string[] tables, string[] views, bool? useSPs, string providerName);

        /// <summary>
        /// Gets the list of tables.
        /// </summary>
        /// <param name="providerName">
        /// The name of the provider to get the tables for.
        /// If null, uses the default provider.
        /// </param>
        /// <returns>A list of table names.</returns>
        string[] GetTableNames(string providerName);

        /// <summary>
        /// Gets the list of views.
        /// </summary>
        /// <param name="providerName">
        /// The name of the provider to get the views for.
        /// If null, uses the default provider.
        /// </param>
        /// <returns>A list of view names.</returns>
        string[] GetViewNames(string providerName);

        /// <summary>
        /// Gets the list of provider names for the current application context.
        /// </summary>
        /// <returns>A list of provider names.</returns>
        string[] GetProviderNames();

        #endregion Methods
    }

    /// <summary>
    /// Hosts the code generation process for any environment.
    /// </summary>
    /// <remarks>
    /// This host allows multiple SubSonic code generators 
    /// to run simultaneously even if the SubSonic core classes are static.
    /// </remarks>
    public class CompileUnitGeneratorHost //: ICompileUnitGenerator
    {
        #region Fields

        /// <summary>
        /// The type of the internal generator.
        /// </summary>
        /// <remarks>
        /// More reliable than using a hard-coded string.
        /// </remarks>
        private static Type _generatorType = typeof(CompileUnitGenerator);

        /// <summary>
        /// The hosted compile unit generator.
        /// </summary>
        private ICompileUnitGenerator _generator = null;

        #endregion Fields


        #region Constructors

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="path">The path to the project to create the host for.</param>
        public CompileUnitGeneratorHost(string path)
        {
            _generator = CreateGenerator(path);
        }

        #endregion Constructors


        #region Properties

        /// <summary>
        /// Gets the hosted compile unit generator.
        /// </summary>
        public ICompileUnitGenerator Generator
        {
            get { return _generator; }
        }

        #endregion Properties


        #region Methods

        /// <summary>
        /// Creates a CompileUnitGenerator in its own domain.
        /// </summary>
        /// <remarks>
        /// Assigns the configuration file with the highest priority to the newly created AppDomain.
        /// </remarks>
        /// <param name="path">The path to the project to create the generator for.</param>
        /// <returns>The CompileUnitGenerator.</returns>
        [MethodImpl(MethodImplOptions.NoInlining)] // Make sure the method is not inlined by the compiler.
        protected static ICompileUnitGenerator CreateGenerator(string path)
        {
            AppDomainSetup setup = new AppDomainSetup();
            string[] configFiles = ConfigurationProvider.FindProjectConfigFiles(path);

            setup.ApplicationBase = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            setup.ConfigurationFile = configFiles[0]; // Make System.Configuration.ConfigurationManager use this file in the new AppDomain.
            AppDomain domain = AppDomain.CreateDomain(Guid.NewGuid().ToString(), null, setup);

            return domain.CreateInstanceAndUnwrap(_generatorType.Assembly.FullName, _generatorType.FullName, false, BindingFlags.CreateInstance | BindingFlags.Instance | BindingFlags.Public, null, new object[] { configFiles }, null, null, null) as ICompileUnitGenerator;
        }

        #endregion Methods


        #region Classes

        /// <summary>
        /// Configuration-wise code generator.
        /// </summary>
        /// <remarks>An instance of this class is not thread safe.</remarks>
        private class CompileUnitGenerator : MarshalByRefObject, ICompileUnitGenerator
        {
            #region Constructors

            /// <summary>
            /// Constructor.
            /// </summary>
            /// <param name="configFiles">The configuration files to use as override.</param>
            public CompileUnitGenerator(string[] configFiles)
            {
                // Use the config files as overrides of the hosting process.
                ConfigurationProvider.CurrentInstance.ConfigFileOverrides = configFiles;

                // Hack to support relative path for code templates.
                // Only support current directory relativity (starting with "." or ".\").
                DataService.LoadProviders(); // This is where SubSonic init its configuration... load it now.
                if (!string.IsNullOrEmpty(SubSonicConfig.TemplateDirectory) && (SubSonicConfig.TemplateDirectory == "." || SubSonicConfig.TemplateDirectory.StartsWith(@".\")))
                {
                    string directory = Path.GetDirectoryName(ConfigurationProvider.CurrentInstance.ConfigFileOverrides[0]);
                    if (!directory.EndsWith(Path.DirectorySeparatorChar.ToString()))
                        directory += Path.DirectorySeparatorChar;
                    if (SubSonicConfig.TemplateDirectory == ".")
                        SubSonicConfig.TemplateDirectory = directory;
                    else
                        SubSonicConfig.TemplateDirectory = directory + SubSonicConfig.TemplateDirectory.Substring(2);
                }
            }

            #endregion Constructors


            #region Methods

            /// <summary>
            /// Generates the DAL in a single CodeCompileUnit from an input file.
            /// </summary>
            /// <param name="languageType">The language to generate the DAL for.</param>
            /// <param name="inputFile">The path to the input file.</param>
            /// <returns>A single CodeCompileUnit.</returns>
            public CodeCompileUnit GenerateSingleUnit(LanguageType languageType, string inputFile)
            {
                BuildProvider builder = new BuildProvider();
                string tableText = Utility.GetFileText(inputFile);

                return builder.GenerateSingleUnit(languageType, tableText);
            }

            /// <summary>
            /// Generates the DAL in multiple CodeCompileUnits for specific tables, views
            /// and, optionnaly, all stored procedures.
            /// </summary>
            /// <param name="languageType">The language to generate the DAL for.</param>
            /// <param name="tables">
            /// The list of tables to generate the DAL for; first element as "*" for all tables.
            /// Can be null or empty. A null value generates no table and no table struct. 
            /// <param name="views">
            /// The list of views to generate the DAL for; first element as "*" for all views.
            /// Can be null or empty. A null value generates no view and no view struct. 
            /// </param>
            /// <param name="useSPs">
            /// true to generate the Stored Procedure; false not to generate.
            /// A null value generates Stored Procedures based on the configuration file option.
            /// </param>
            /// <param name="providerName">
            /// The name of the provider to generate the DAL for.
            /// If null, generates code for all the providers.
            /// </param>
            /// <returns>A dictionary of suggested filenames/CodeCompileUnit pairs.</returns>
            public IDictionary<string, CodeCompileUnit> GenerateMultipleUnits(LanguageType languageType, string[] tables, string[] views, bool? useSPs, string providerName)
            {
                BuildProvider builder = new BuildProvider();
                return builder.GenerateMultipleUnits(languageType, tables, views, useSPs, providerName);
            }

            /// <summary>
            /// Generates the DAL in multiple CodeCompileUnits from an input file.
            /// </summary>
            /// <param name="languageType">The language to generate the DAL for.</param>
            /// <param name="inputFile">The path to the input file.</param>
            /// <returns>A dictionary of suggested filenames/CodeCompileUnit pairs.</returns>
            public IDictionary<string, CodeCompileUnit> GenerateMultipleUnits(LanguageType languageType, string inputFile)
            {
                string tableText = Utility.GetFileText(inputFile);
                string[] tables = tableText.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
                string[] views = new string[] { "*" };

                // Use input file for tables; always generate views; generate SP based on config file.
                return GenerateMultipleUnits(languageType, tables, views, null, null);
            }

            /// <summary>
            /// Gets the list of tables.
            /// </summary>
            /// <param name="providerName">
            /// The name of the provider to get the tables for.
            /// If null, uses the default provider.
            /// </param>
            /// <returns>A list of table names.</returns>
            public string[] GetTableNames(string providerName)
            {
                //if (String.IsNullOrEmpty(providerName))
                //    return DataService.GetTableNames(providerName);
                //else
                    return DataService.GetTableNames(providerName);
            }

            /// <summary>
            /// Gets the list of views.
            /// </summary>
            /// <param name="providerName">
            /// The name of the provider to get the views for.
            /// If null, uses the default provider.
            /// </param>
            /// <returns>A list of view names.</returns>
            public string[] GetViewNames(string providerName)
            {
                //if (String.IsNullOrEmpty(providerName))
                //    return DataService.GetViewNames();
                //else
                    return DataService.GetViewNames(providerName);
            }

            /// <summary>
            /// Gets the list of provider names for the current application context.
            /// </summary>
            /// <returns>A list of provider names.</returns>
            public string[] GetProviderNames()
            {
                List<string> providerNames = new List<string>();
                foreach(DataProvider provider in DataService.Providers)
                    providerNames.Add(provider.Name);
                return providerNames.ToArray();
            }

            #endregion Methods
        }

        #endregion Classes
    }
}
